﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PubSub.S3.Abstract;
using PubSub.S3.Abstract.DistributeEventBus;
using PubSub.S3.Common;
using PubSub.S3.EventName;
using StackExchange.Redis;

namespace PubSub.S3.Redis
{
    /// <summary>
    /// 基于redis的事件总线
    /// </summary>
    public class RedisEventBus : DistributedEventBusAbstract
    {
        protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; private set; }
        protected ConcurrentDictionary<string, Type> EventTypes { get; private set; }
        private static ConnectionMultiplexer _connectionMultiplexer;
        private static ISubscriber _pubSubscriber;
        private static ISubscriber _subSubscriber;
        private HashSet<string> _subscribeChannels = new HashSet<string>();

        public RedisEventBus()
        {
            EventTypes = new ConcurrentDictionary<string, Type>();
            HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>();

            Initialize();
        }

        /// <summary>
        /// 初始化
        /// </summary>
        public void Initialize()
        {
            _connectionMultiplexer =
                RedisHelper.ConnectionRedis(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6379), string.Empty);
            _pubSubscriber = _connectionMultiplexer.GetSubscriber();
            _subSubscriber = _connectionMultiplexer.GetSubscriber();
        }

        protected override async Task PublishToEventBusAsync(Type eventType, EventDataAbstract eventData)
        {
            var eventName = EventNameAttribute.GetNameOrDefault(eventType);
            if (string.IsNullOrWhiteSpace(eventName))
                throw new ArgumentException(nameof(eventType));

            var redisChannel = new RedisChannel(eventName, RedisChannel.PatternMode.Auto);
            var body = JsonConvert.SerializeObject(eventData);
            await _pubSubscriber.PublishAsync(redisChannel, body);
        }


        public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory)
        {
            var handlerFactories = GetOrCreateHandlerFactories(eventType);
            if (factory.IsInFactories(handlerFactories))
            {
                return NullDisposable.Instance;
            }
            handlerFactories.Add(factory);

            var eventName = EventNameAttribute.GetNameOrDefault(eventType);
            if (string.IsNullOrWhiteSpace(eventName))
                throw new ArgumentException(nameof(eventType));

            if (_subscribeChannels.Contains(eventName))
            {
                // return NullDisposable.Instance;
                return new EventHandlerFactoryUnregistrar(this, eventType, factory);
            }
            _subscribeChannels.Add(eventName);

            var redisChannel = new RedisChannel(eventName, RedisChannel.PatternMode.Auto);
            _subSubscriber.Subscribe(redisChannel, async (channel, redisValue) =>
            {
                try
                {
                    if (redisChannel.IsNullOrEmpty)
                    {
                        //todo 无效channel
                    }
                    else
                    {
                        if (!redisValue.HasValue)
                        {
                            //todo 无效数据
                        }
                        else
                        {
                            var receiveEventName = channel.ToString(); //处理消息
                            if (EventTypes.TryGetValue(receiveEventName, out Type receiveEventType))
                            {
                                var json = redisValue.ToString();
                                if (!string.IsNullOrEmpty(json))
                                {
                                    var obj = JsonConvert.DeserializeObject(json, receiveEventType);
                                    var eventData = obj as EventDataAbstract;
                                    if (eventData == null)
                                    {
                                        //todo 错误格式数据
                                    }
                                    else
                                    {
                                        await TriggerHandlersAsync(receiveEventType, eventData);
                                    }
                                }
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            });

            return new EventHandlerFactoryUnregistrar(this, eventType, factory);
        }

        private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType)
        {
            return HandlerFactories.GetOrAdd(
                eventType,
                type =>
                {
                    var eventName = EventNameAttribute.GetNameOrDefault(type);
                    EventTypes[eventName] = type;
                    return new List<IEventHandlerFactory>();
                }
            );
        }

        protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType)
        {
            var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>();

            foreach (var handlerFactory in
                HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
            {
                handlerFactoryList.Add(
                    new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));
            }

            return handlerFactoryList.ToArray();
        }

        private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType)
        {
            //Should trigger same type
            if (handlerEventType == targetEventType)
            {
                return true;
            }

            //TODO: Support inheritance? But it does not support on subscription to RabbitMq!
            //Should trigger for inherited types
            if (handlerEventType.IsAssignableFrom(targetEventType))
            {
                return true;
            }

            return false;
        }

        /// <inheritdoc/>
        public override void Unsubscribe<TEvent>(Func<TEvent, Task> action)
        {
            if (action == null) throw new ArgumentNullException(nameof(action));

            GetOrCreateHandlerFactories(typeof(TEvent))
                .Locking(factories =>
                {
                    factories.RemoveAll(
                        factory =>
                        {
                            var singleInstanceFactory = factory as SingleInstanceHandlerFactory;
                            if (singleInstanceFactory == null)
                            {
                                return false;
                            }

                            var actionHandler = singleInstanceFactory.HandlerInstance as ActionEventHandler<TEvent>;
                            if (actionHandler == null)
                            {
                                return false;
                            }

                            return actionHandler.Action == action;
                        });
                });
        }

        /// <inheritdoc/>
        public override void Unsubscribe(Type eventType, IEventHandler handler)
        {
            GetOrCreateHandlerFactories(eventType)
                .Locking(factories =>
                {
                    factories.RemoveAll(
                        factory =>
                            factory is SingleInstanceHandlerFactory &&
                            (factory as SingleInstanceHandlerFactory).HandlerInstance == handler
                    );
                });
        }

        /// <inheritdoc/>
        public override void Unsubscribe(Type eventType, IEventHandlerFactory factory)
        {
            GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Remove(factory));
        }

        /// <inheritdoc/>
        public override void UnsubscribeAll(Type eventType)
        {
            GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear());
        }
    }
}